/**
 * 1、pointer-event: none：该元素永远不会成为鼠标事件的 target,
 * 但是，当其后代元素的 pointer-events 属性指定其它值时，鼠标事件可以指向后代元素，在这种情况下，鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器
 * 2、所以：使用pointer-events来阻止元素成为鼠标事件目标不一定意味着元素上的事件侦听器永远不会触发，
 * 如果元素后代明确指定了pointer-events属性并允许其成为鼠标事件的目标
 */
import React, {
  useState,
  useRef,
} from 'react'
import classNames from 'classnames'
import { useSpring, animated } from '@react-spring/web'
import { useDrag } from '@use-gesture/react'
import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react'
import { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect'
import { useUnmountedRef } from '../../hooks/use-unmounted-ref'
import { useInnerVisible } from '../../hooks/use-inner-visible'
import { useLockScroll } from '../../hooks/use-lock-scroll'
import { withStopPropagation } from '../../utils/with-stop-propagation'
import { withNativeProps } from '../../utils/native-props'
import { ShouldRender } from '../../utils/should-render'
import { renderToContainer } from '../../utils/render-to-container'
import { Mask } from '../Mask'
import { PopupProps } from './type'

const classPrefix = 'rt-popup'

export const Popup: React.FC<PopupProps> = (props) => {
  const {
    afterShow,
    afterClose,
    bodyClassName,
    bodyStyle,
    children,
    closeOnMaskClick = true,
    destroyOnClose = false,
    disableBodyScroll = true,
    forceRender = false,
    getContainer = () => document.body,
    mask = true,
    maskClassName,
    maskStyle,
    showCloseButton = true,
    stopPropagation = ['click'],
    closeOnSwipe = false,
    position = 'bottom',
    onClick,
    onClose,
    onMaskClick,
    visible = false,
  } = props

  const { t: translate } = useTranslation()

  const bodyClass = classNames(
    `${classPrefix}-body`,
    bodyClassName,
    `${classPrefix}-body-position-${position}`,
  )

  const ref = useRef<HTMLDivElement>(null)

  const [active, setActive] = useState(visible)

  useLockScroll(ref, disableBodyScroll && active ? 'strict' : false)

  useIsomorphicLayoutEffect(() => {
    if (visible) {
      setActive(true)
    }
  }, [visible])

  const unmountedRef = useUnmountedRef()

  const { percent } = useSpring({
    percent: visible ? 0 : 100,
    config: {
      precision: 0.01,
      mass: 0.4,
      tension: 250,
      friction: 30,
    },
    onRest: () => {
      if (unmountedRef.current) return
      setActive(visible)
      visible ? afterShow?.() : afterClose?.()
    },
  })

  const bind = useDrag(
    ({ swipe: [, swipeY] }) => {
      if (!closeOnSwipe) return
      if (
        (swipeY === 1 && position === 'bottom')
        || (swipeY === -1 && position === 'top')
      ) {
        onClose?.()
      }
    },
    {
      axis: 'y',
      enabled: ['top', 'bottom'].includes(position),
    },
  )

  /**
   * 使用 active 的问题
   * 1、每次点击显示popup: mask 会瞬间出现.（destroyOnClose = true 的情况下）
   * 原因: 当shouleRender 执行 active === true 加载dom时, mask 组件的visible=true 已经成立了.没有从 false-> true 的过程， mask所有动画不会在执行了.
   * 如果 destroyOnClose === false, 则只会在首次点击会出现 mask立马出现的情况，当第二次点击时由于整个dom 并未销毁，mask组件会有从false->true 的过程
   * 2、点击点击关闭popup: mask 会等到真正的内容完全消失才会消失，且没有动画.（destroyOnClose = true 的情况下）
   * 原因: 当popup内部动画执行完成以后 active === false, 整个组件已经卸载, 不会有任何动画. 离场动画来不及播，用户会看到「瞬间消失」的闪屏.
   * 如果 destroyOnClose === false, 还是会出现相同的现象. 原因是该代码 `display: active ? undefined : 'none',`, active 从 true→false 的瞬间，React 在同一帧把 display 设为 'none'，浏览器直接销毁节点 → 动画被截断
   * 问题？： display： none  浏览器会销毁节点？
   * 回答：不会真正销毁 DOM 节点，但效果上“相当于”被浏览器立即隐藏、不再参与布局/绘制/事件 并且：
   * CSS 动画/过渡会被立刻中断（因为元素进入不可见状态，浏览器认为不需要继续渲染）
   * React-Spring / 其他 JS 动画引擎如果依赖“当前帧”来计算起始值，也会因为 display: none 导致起始值丢失，从而出现“跳帧”或“瞬间完成”
   * 所以日常口语里会说“销毁”，本质是：display: none 让元素立刻脱离渲染管线，所有基于该节点的动画都会被浏览器当成“已结束”，看起来就像被“销毁”了
   * ----------------------------------------------
   * 使用 visible 的问题
   * 1、每次点击显示popup: mask 会瞬间出现.（destroyOnClose = true 的情况下）
   * 原因: 当shouleRender 执行 active === true 加载dom时, mask 组件的visible=true。已经成立了.没有从 false-> true 的过程， mask所有动画不会在执行了.
   * 如果 destroyOnClose === false, 则只会在首次点击会出现 mask立马出现的情况，当第二次点击时由于整个dom 并未销毁，mask组件会有从false->true 的过程
   * ----------------------------------------------
   * useInnerVisible 为什么可以：
   * 把「第一次渲染」强制推迟了一帧，让 Mask 的 visible 在首帧先保持 false，下一帧才真正变为 true，于是 React-Spring 能看到 false → true 的变化，从而正常播放入场动画。
   * 如果不用 useInnerVisible 直接把 visible={active && visible} 给 Mask：首帧就是 true，React-Spring 认为“无需过渡”，于是直接渲染到 1，看起来就是“瞬间出现”。
   */
  const maskVisible = useInnerVisible(active && visible)

  const node = withStopPropagation(
    stopPropagation,
    withNativeProps(
      props,
      <div
        className={classPrefix}
        onClick={onClick}
        style={{
          display: active ? undefined : 'none',
          touchAction: ['top', 'bottom'].includes(position)
            ? 'none'
            : 'auto',
        }}
        {...bind()}
      >
        {mask && (
          <Mask
            className={classNames(`${classPrefix}-mask`, maskClassName)}
            disableBodyScroll={disableBodyScroll}
            destroyOnClose={destroyOnClose}
            forceRender={forceRender}
            onMaskClick={(e) => {
              onMaskClick?.(e)
              if (closeOnMaskClick) {
                onClose?.()
              }
            }}
            stopPropagation={stopPropagation}
            style={maskStyle}
            visible={maskVisible}
          />
        )}
        <animated.div
          className={bodyClass}
          ref={ref}
          style={{
            ...bodyStyle,
            pointerEvents: percent.to((v) => (v === 0 ? 'unset' : 'none')),
            transform: percent.to((v) => {
              if (position === 'bottom') {
                return `translate(0, ${v}%)`
              }
              if (position === 'top') {
                return `translate(0, -${v}%)`
              }
              if (position === 'left') {
                return `translate(-${v}%, 0)`
              }
              if (position === 'right') {
                return `translate(${v}%, 0)`
              }
              return 'none'
            }),
          }}
        >
          {showCloseButton && (
            <span
              className={classNames(
                `${classPrefix}-close-icon`,
                'rt-plain-anchor',
              )}
              onClick={() => {
                onClose?.()
              }}
              role="button"
              tabIndex={0}
              aria-label={translate(('common.close'))}
            >
              <RiCloseLine />
            </span>
          )}
          {children}
        </animated.div>
      </div>,
    ),
  )

  return (
    <ShouldRender
      active={active}
      forceRender={forceRender}
      destroyOnClose={destroyOnClose}
    >
      {renderToContainer(getContainer, node)}
    </ShouldRender>
  )
}
